在 Vue 應用開發中,表單驗證是一項至關重要的功能,尤其是當表單數據變得複雜且需要高度自定義時。Zod 作為一個強大的 JavaScript 驗證庫,可以與 Vee-Validate 無縫結合,提供靈活且強大的運行時驗證能力。本文將介紹如何使用 Zod 的 refine
、superRefine
、safeParse
和 safeParseAsync
,結合 Vee-Validate 和 @vee-validate/zod
,實現 email、IP、台灣電話號碼等複雜驗證,並進行異步驗證操作。
Zod 提供了多種高階驗證方法,其中 refine
和 superRefine
可以幫助我們在 Schema 中實現自定義驗證邏輯。以下是定義表單驗證的 Zod schema,包含 email、IP、url、地址、台灣電話號碼,以及使用 fetch API 進行的異步驗證。
(檔案: src/schemas/userFormSchema.ts
)
import * as zod from 'zod';
import { useDebounceFn } from '@vueuse/core'; // 後續會介紹講到 vueuse 的部分
export const userFormSchema = zod
.object({
email: zod.string().email('請輸入有效的電子郵件地址'),
imageUrl: zod.string().url('請輸入有效的圖片 url'),
ip: zod.string().ip('請輸入有效的 ip 位置'),
taiwanPhone: zod.string().refine((value) => {
const taiwanPhoneRegex = /^09\d{8}$/;
return taiwanPhoneRegex.test(value);
}, '請輸入有效的台灣電話號碼'),
token: zod.string().refine(useDebounceFn(async (value) => {
// 模擬 API 驗證
const response = await fetch(`https://api.example.com/validate?token=${value}`);
const data = await response.json();
return data.isValid;
}, '驗證失敗,請輸入有效 token'),
password: zod.string(),
confirmPassword: zod.string()
}, 800))
.superRefine(({ password, confirmPassword }, ctx) => {
if (password === confirmPassword) {
ctx.addIssue({
code: zod.ZodIssueCode.custom,
message: '確認密碼和密碼不符',
path: ['password', 'confirmPassword'],
});
}
});
export type UserFormSchema = zod.infer<typeof userFormSchema>;
這個 schema 使用了 refine
來驗證台灣電話號碼,以及一個需要異步驗證的 token
。superRefine
用於更複雜的驗證邏輯,例如密碼
和確認密碼
必須一致。
接下來,我們將使用 useForm
和 useField
在 Vue 中進行表單驗證,並將 Zod schema 結合到 Vee-Validate。
(檔案: src/components/userFormComponent.vue
)
<script lang="ts" setup>
import { useUserForm } from '../composables/useUserForm';
const {
// state::
errors,
// field::
email,
imageUrl,
ip,
taiwanPhone,
token,
password,
confirmPassword,
// methods::
submitForm
} = useUserForm();
</script>
<template>
<form @submit.prevent="submitForm">
<div>
<label for="email">電子郵件</label>
<input id="email" v-model="email" />
<span v-if="errors.email">{{ errors.email }}</span>
</div>
<div>
<label for="imageUrl">圖片網址</label>
<input id="imageUrl" v-model="imageUrl" />
<span v-if="errors.imageUrl">{{ errors.imageUrl }}</span>
</div>
<div>
<label for="ip">IP 地址</label>
<input id="ip" v-model="ip" />
<span v-if="errors.ip">{{ errors.ip }}</span>
</div>
<div>
<label for="taiwanPhone">台灣電話號碼</label>
<input id="taiwanPhone" v-model="taiwanPhone" />
<span v-if="errors.taiwanPhone">{{ errors.taiwanPhone }}</span>
</div>
<div>
<label for="token">驗證信 token</label>
<input id="token" v-model="token" />
<span v-if="errors.token">{{ errors.token }}</span>
</div>
<div>
<label for="password">密碼</label>
<input id="password" v-model="password" />
<span v-if="errors.password">{{ errors.password }}</span>
</div>
<div>
<label for="confirmPassword">確認密碼</label>
<input id="confirmPassword" v-model="confirmPassword" />
<span v-if="errors.confirmPassword">{{ errors.confirmPassword }}</span>
</div>
<button type="submit">提交</button>
</form>
</template>
(檔案: src/composables/useUserForm.ts
)
import { useForm, useField } from 'vee-validate';
import { toTypedSchema } from '@vee-validate/zod';
import { userFormSchema, type UserFormSchema } from '../schemas/userFormSchema';
export const useUserForm = () => {
const validationSchema = toTypedSchema(userFormSchema);
const initialValues: UserFormSchema = {
email: '',
imageUrl: '',
ip: '',
taiwanPhone: '',
token: '',
password: '',
confirmPassword: '',
};
// 設置 Vee-Validate 的 useForm 並綁定 Zod schema
const { handleSubmit, errors } = useForm({
validationSchema,
initialValues
});
// 使用 useField 將每個字段的驗證邏輯和 UI 結合
const email = useField<string>('email');
const imageUrl = useField<string>('imageUrl');
const ip = useField<string>('ip');
const taiwanPhone = useField<string>('taiwanPhone');
const token = useField<string>('asyncValue');
const password = useField<string>('password');
const confirmPassword = useField<string>('confirmPassword');
const submitForm = handleSubmit((values) => {
console.log('表單驗證成功:', values);
});
return {
// state::
errors,
// field::
email,
imageUrl,
ip,
taiwanPhone,
token,
password,
confirmPassword,
// methods::
submitForm
};
};
export type UseUserForm = typeof useUserForm;
Zod 的 safeParse
和 safeParseAsync
讓我們能夠在提交表單前或其他情況下手動驗證數據,這樣可以靈活處理表單提交的驗證流程。
// 手動驗證數據的範例
import { formSchema } from '../schemas/userFormSchema';
// 同步驗證數據,因為 refine內有非同步動作,所以建議用 safeParseAsync 進行驗證
// 這裡僅是示範
const result = userFormSchema.safeParse({
email: 'user@example.com',
imageUrl: 'http://www.exampleImage.com/test.jpg',
ip: '192.168.0.1',
taiwanPhone: '0912345678',
token: 'someValue',
password: 'hello password',
confirmPassword: 'hello password',
});
if (!result.success) {
console.error('同步驗證失敗:', result.error.errors);
} else {
console.log('同步驗證成功:', result.data);
}
// 異步驗證數據
const asyncResult = await userFormSchema.safeParseAsync({
email: 'user@example.com',
imageUrl: 'http://www.exampleImage.com/test.jpg',
ip: '192.168.0.1',
taiwanPhone: '0912345678',
token: 'someValue',
password: 'hello password',
confirmPassword: 'hello password',
});
if (!asyncResult.success) {
console.error('異步驗證失敗:', asyncResult.error.errors);
} else {
console.log('異步驗證成功:', asyncResult.data);
}
結合 Zod 的強大驗證功能和 Vee-Validate 的靈活性,我們可以實現複雜且高效的動態表單驗證。通過 refine
和 superRefine
,可以實現自定義驗證邏輯,而 safeParse
和 safeParseAsync
則提供了更多控制驗證流程的方式。這些功能使得我們在處理高階組件設計時能夠更加從容和靈活。
希望這篇文章能幫助你在 Vue 項目中更好地應用 Zod 和 Vee-Validate 進行動態表單驗證!